<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>MyCalendar</title>
  </head>

  <body>
    <div id="root"></div>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <!-- <script src="./modules/react.development.js"></script>
    <script src="./modules/react-dom.development.js"></script>
    <script src="./modules/babel.min.js"></script> -->
    <script type="text/babel">
      // 传入两个时间（年月日），判断第二个是否在第一个的时间后
      const isAfterDate = (beJudged = {}, target = {}, equal = false) => {
        if (
          beJudged.year &&
          (beJudged.month || beJudged.month === 0) &&
          beJudged.day &&
          target.year &&
          (target.month || target.month === 0) &&
          target.day
        ) {
          return (
            target.year > beJudged.year ||
            (target.year === beJudged.year && target.month > beJudged.month) ||
            (target.year === beJudged.year &&
              target.month === beJudged.month &&
              (equal ? target.day >= beJudged.day : target.day > beJudged.day))
          );
        }
      };
      // 传入三个时间（年月日），判断时间是否在两个时间之内
      const isWithinDate = (
        obj = {}, // 需要判断的时间
        [start = {}, end = {}] = [], // 开始时间、结束时间
        { leftEqual = true, rightEqual = true } = {} // 一些配置
      ) => {
        return (
          isAfterDate(start, obj, rightEqual) &&
          isAfterDate(obj, end, leftEqual)
        );
      };
      // 根据 字符串/时间戳 提取 年月日
      const extractDate = (date) => {
        if (date === "today") {
          const today = new Date();
          return [today.getFullYear(), today.getMonth() + 1, today.getDate()];
        } else if (typeof date === "string") {
          const res =
            date.indexOf("-") !== -1
              ? date.split("-")
              : date.indexOf("/") !== -1
              ? date.split("/")
              : [];
          return [
            !isNaN(Number(res[0])) ? Number(res[0]) : res[0],
            !isNaN(Number(res[1])) ? Number(res[1]) : res[1],
            !isNaN(Number(res[2])) ? Number(res[2]) : res[2],
          ];
        } else if (typeof date === "number") {
          return [];
        } else {
          return [];
        }
      };

      /*
         年的展示数组：
         [
           {
             value: 2022,
             month: [
               {
                 value: 1
                 day: [{ value: 1 }, { value: 2 }, ... , { value: 31 },]
               },
               ... ,
             ]
           },
           ...
         ]
       */
      /*
         事件的传入数组：
         [
           {
             id: 'abcd',
             begin: '2022-09-07', // 或者时间戳？
             end: '2022-09-08',
             color: '#bfc',
             remark: '备注备注',
           }
         ]
         事件处理后的对象（因为想减少遍历计算，根据年月日直接读对象属性）
          {
            '2022-9-7': [{ color: '#bfc', remark: '备注备注', id: 'abcd' }, { color: 'red', remark: '其他事件', id: 'bbcd' }],
            '2022-9-8': [{ color: '#bfc', remark: '备注备注', id: 'abcd' }],
          }
        */

      const MyCalendar = ({
        years = [2022], // 需要显示的年份数组
        eventList = [], // 事件数组
        dayWidth = 10,
        dayMargin = 2,
        dayBorderRadius = 3,
        SunDayFirst = true, // 默认周日排最前，为否时周一排最前
        horizontalMode = true, // 默认水平排列，为否时竖向排列
        yearStyle = {}, // 年样式
        monthTitle = true, // 月标题： boolean|function|any
        monthTitleStyle = {}, // 月标题样式
        monthStyle = {}, // 月样式
        dayStyle = {}, // 天样式
        weekendStyle = {}, // 周末样式
        defaultColor = "gray", // 默认天的颜色
        todayColor = "orange", // 今天的颜色
        afterTodayOpacity = 0.3, // 今天之后的透明度
        onMouseOver = null, // 对每天的移动事件
      }) => {
        const monthWidth = (dayWidth + dayMargin) * 7; // 一周的宽度

        // 当天的 Date 信息
        const todayDate = React.useRef(new Date());
        const today = React.useRef({
          year: todayDate.current.getFullYear(),
          month: todayDate.current.getMonth(),
          day: todayDate.current.getDate(),
          dayWeek: todayDate.current.getDay(),
        });

        // 生成日期
        const resYears = React.useMemo(() => {
          return Array.isArray(years)
            ? years.map((year) => ({
                value: year,
                month: new Array(12).fill(1).map((_, monthIndex) => {
                  const month = monthIndex;
                  const days = new Date(year, month + 1, 0).getDate(); // 获取月天数需要 month + 1（因为data置0了）
                  const dayWeekInFirstDay = new Date(year, month, 1).getDay(); // 获取当月第一天就不需要 + 1（0~11）
                  return {
                    value: month,
                    day: new Array(days).fill(1).map((_, dayIndex) => ({
                      value: dayIndex + 1,
                      dayWeek: (dayIndex + dayWeekInFirstDay) % 7,
                    })),
                  };
                }),
              }))
            : [];
        }, [years]);

        // 事件和日期对接上
        const resEvents = React.useMemo(() => {
          const res = {};
          if (Array.isArray(resYears) && Array.isArray(eventList)) {
            resYears.forEach((year) => {
              Array.isArray(year?.month) &&
                year.month.forEach((month) => {
                  month?.day.forEach((day) => {
                    eventList.forEach((event) => {
                      const [startYear, startMonth, startDay] = extractDate(
                        event.begin
                      );
                      const [endYear, endMonth, endDay] = extractDate(
                        event.end
                      );
                      const isWithin = isWithinDate(
                        {
                          year: year.value,
                          month: month.value + 1,
                          day: day.value,
                        },
                        [
                          { year: startYear, month: startMonth, day: startDay },
                          { year: endYear, month: endMonth, day: endDay },
                        ]
                      );
                      if (isWithin) {
                        const key = `${year.value}-${month.value}-${day.value}`;
                        const value = { ...event };
                        if (Array.isArray(res[key])) {
                          res[key].push(value);
                        } else {
                          res[key] = [value];
                        }
                      }
                    });
                  });
                });
            });
            return res;
          } else {
            return res;
          }
        }, [resYears, eventList]);

        return (
          <div>
            {resYears.map((year) => (
              <div key={year?.value}>
                {year.value}年{" "}
                {SunDayFirst ? "日一二三四五六" : "一二三四五六日"}
                <div
                  style={{ display: "flex", flexWrap: "wrap", ...yearStyle }}
                >
                  {year?.month.map((month) => {
                    const dayWeek = month?.day?.[0]?.dayWeek || 0;
                    const blanks = SunDayFirst
                      ? dayWeek
                      : (dayWeek - 1 + 7) % 7; // 确保减一后在七的范围内
                    const blanksArr = new Array(blanks).fill("");
                    // 副轴的宽度
                    const verticalWidth =
                      Math.ceil((blanks + month?.day.length || 0) / 7) *
                      (dayWidth + dayMargin);
                    return (
                      <div
                        style={{ marginLeft: "8px", ...monthStyle }}
                        key={month?.value}
                      >
                        <div style={{ ...monthTitleStyle }}>
                          {typeof monthTitle === "boolean"
                            ? monthTitle && `${month?.value + 1}月`
                            : typeof monthTitle === "function"
                            ? monthTitle(month?.value)
                            : monthTitle}
                        </div>
                        <div
                          style={{
                            ...(horizontalMode
                              ? { width: monthWidth, height: verticalWidth }
                              : { width: verticalWidth, height: monthWidth }),
                            display: "flex",
                            flexDirection: horizontalMode ? "row" : "column",
                            flexWrap: "wrap",
                          }}
                        >
                          {blanksArr.map((blank, index) => (
                            <div
                              key={index}
                              style={{
                                width: dayWidth,
                                height: dayWidth,
                                margin: `0 ${dayMargin}px ${dayMargin}px 0`,
                                opacity: 0,
                              }}
                            ></div>
                          ))}
                          {month?.day.map((day) => {
                            const isToday =
                              today.current.year === year.value &&
                              today.current.month === month.value &&
                              today.current.day === day.value;
                            const isAfterToday = isAfterDate(
                              {
                                year: today.current.year,
                                month: today.current.month,
                                day: today.current.day,
                              },
                              {
                                year: year.value,
                                month: month.value,
                                day: day.value,
                              }
                            );

                            // 这天包含的事件
                            const events =
                              resEvents[
                                `${year.value}-${month.value}-${day.value}`
                              ];

                            // 这天的背景颜色
                            const backgroundColor = isToday
                              ? todayColor
                              : Array.isArray(events) && events.length > 0
                              ? events[0]?.color || defaultColor
                              : defaultColor;
                            const linearBackground =
                              Array.isArray(events) &&
                              events.length > 1 &&
                              `linear-gradient(to right bottom, ${events
                                .filter((event) => event.color)
                                .map((event) => event.color)
                                .join(",")})`;

                            return (
                              <div
                                key={day.value}
                                style={{
                                  width: dayWidth,
                                  height: dayWidth,
                                  margin: `0 ${dayMargin}px ${dayMargin}px 0`,
                                  backgroundColor,
                                  ...(Array.isArray(events) &&
                                    events.length > 1 && {
                                      backgroundImage: linearBackground,
                                    }),
                                  borderRadius: dayBorderRadius,
                                  ...dayStyle,
                                  ...((day.dayWeek === 6 ||
                                    day.dayWeek === 0) &&
                                    weekendStyle),
                                  ...(isAfterToday && {
                                    opacity: afterTodayOpacity,
                                  }),
                                }}
                                {...(typeof onMouseOver === "function" && {
                                  onMouseOver: (e) =>
                                    onMouseOver(e, {
                                      year: year.value,
                                      month: month.value,
                                      day: day.value,
                                      dayWeek: day.dayWeek,
                                      events,
                                    }),
                                })}
                              ></div>
                            );
                          })}
                        </div>
                      </div>
                    );
                  })}
                </div>
              </div>
            ))}
          </div>
        );
      };

      const years = new Array(27).fill(null).map((_, index) => index + 1997);
      // const years = [2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023];
      const eventList = [
        {
          id: '00',
          begin: "1997-02-14",
          end: "1997-02-14",
          color: "#FFFFFF",
          remark: "出生",
        },
        {
          id: '01',
          begin: "2003-09-01",
          end: "2009-06-30",
          color: "#f79294",
          remark: "上西小学/儒林一小",
        },
        {
          id: '02',
          begin: "2009-09-01",
          end: "2012-06-30",
          color: "#f7c892",
          remark: "佛山儒林中学",
        },
        {
          id: '03',
          begin: "2012-09-01",
          end: "2015-06-30",
          color: "#f1e261",
          remark: "佛山南海中学",
        },
        {
          id: "04",
          begin: "2015-9-1",
          end: "2019-6-25",
          color: "#64db65",
          remark: "广州大学",
        },
        {
          id: "05",
          begin: "2019-7-6",
          end: "2020-3-7",
          color: "#87ebeb",
          remark: "惠州博罗欣旺达",
        },
        {
          id: "06",
          begin: "2020-6-01",
          end: "2021-3-1",
          color: "#87bdeb",
          remark: "广州健保科技",
        },
        {
          id: "07",
          begin: "2021-12-13",
          end: "today",
          color: "#8987eb",
          remark: "珠海国粒教育",
        },
      ];

      const App = () => {
        const [SunDayFirst, setSunDayFirst] = React.useState(false);
        const [mode, setMode] = React.useState(true);
        const [dayInfo, setDayInfo] = React.useState({});
        return (
          <div>
            <button onClick={() => setSunDayFirst((pre) => !pre)}>转换</button>
            以{SunDayFirst ? "周日" : "周一"}为开始
            <br />
            <button onClick={() => setMode((pre) => !pre)}>转换</button>
            {mode ? "当前横向排列" : "当前竖向排列"}
            <br />
            <MyCalendar
              years={years}
              eventList={eventList}
              dayWidth={10}
              dayMargin={2}
              dayBorderRadius={2}
              SunDayFirst={SunDayFirst}
              horizontalMode={mode}
              monthTitle={true}
              defaultColor={"#c0c0c0"}
              afterTodayOpacity={0.5}
              // weekendStyle={{ opacity: 0.5 }} // 周末样式
              onMouseOver={(e, { year, month, day, dayWeek, events }) => {
                setDayInfo({ year, month, day, dayWeek, events });
              }}
            />
            <br />
            <div
              style={{
                position: "fixed",
                top: 100,
                right: 100,
              }}
            >
              <div>
                {eventList.map((item) => (
                  <div
                    key={item.id}
                    style={{ display: "flex", alignItems: "center" }}
                  >
                    <div
                      style={{
                        width: "15px",
                        height: "15px",
                        backgroundColor: item.color,
                        marginRight: "8px",
                      }}
                    ></div>
                    <div>{item.remark}</div>
                  </div>
                ))}
              </div>
              <div style={{ marginTop: "24px" }}>
                日期：{dayInfo?.year}年{dayInfo?.month + 1}月{dayInfo?.day}
                ，星期
                {dayInfo?.dayWeek}
                <div>
                  备注：
                  <div style={{ marginLeft: "24px" }}>
                    {Array.isArray(dayInfo?.events)
                      ? dayInfo.events.map((item) => (
                          <div key={item?.id}>{item?.remark}</div>
                        ))
                      : "无"}
                  </div>
                </div>
              </div>
            </div>
          </div>
        );
      };

      ReactDOM.render(<App />, document.getElementById("root"));
    </script>
  </body>
</html>
